package com.imooc.bilibili.service;

import com.alibaba.fastjson.JSONObject;
import com.imooc.bilibili.dao.UserDao;
import com.imooc.bilibili.domain.PageResult;
import com.imooc.bilibili.domain.RefreshTokenDetail;
import com.imooc.bilibili.domain.User;
import com.imooc.bilibili.domain.UserInfo;
import com.imooc.bilibili.constant.UserConstant;
import com.imooc.bilibili.exception.ConditionException;
import com.imooc.bilibili.util.MD5Util;
import com.imooc.bilibili.util.RSAUtil;
import com.imooc.bilibili.util.TokenUtil;
import com.mysql.cj.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * @author xiexu
 * @create 2022-06-14 18:35
 */
@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    @Autowired
    private UserAuthService userAuthService;

    public void addUser(User user) {
        String phone = user.getPhone();
        if (StringUtils.isNullOrEmpty(phone)) {
            throw new ConditionException("手机号不能为空！");
        }
        // 通过手机号查询相关用户
        User dbUser = this.getUserByPhone(phone);
        if (dbUser != null) {
            throw new ConditionException("该手机号已经被注册了！");
        }
        // 获取当前系统时间戳（充当MD5盐值可以用来加密）
        Date now = new Date();
        // 将时间戳转换成盐值
        String salt = String.valueOf(now.getTime());
        // 这个密码是经过前端RSA公钥加密之后传过来的，需要进行解密
        String password = user.getPassword();
        // 原文密码（解密后的密码）
        String rawPassword;
        try {
            // 对RSA公钥加密后的密钥，进行解密（使用RSA私钥进行解密）
            rawPassword = RSAUtil.decrypt(password); // 返回的rawPassword就是用户的原文密码
        } catch (Exception e) {
            throw new ConditionException("密码解密失败！");
        }
        // 添加用户表
        // 将原文密码进行MD5盐值加密后再添加到数据库中
        String md5Password = MD5Util.sign(rawPassword, salt, "UTF-8");
        user.setSalt(salt);
        user.setPassword(md5Password);
        user.setCreateTime(now);
        userDao.addUser(user); // 此时拿到的user就有自动写入的id了
        // 添加用户信息表
        UserInfo userInfo = new UserInfo();
        userInfo.setUserId(user.getId());
        userInfo.setNick(UserConstant.DEFAULT_NICK); // 一开始是默认信息
        userInfo.setBirth(UserConstant.DEFAULT_BIRTH);
        userInfo.setGender(UserConstant.GENDER_MALE);
        userInfo.setCreateTime(now);
        userDao.addUserInfo(userInfo);
        // 给新注册用户添加一个默认权限角色
        userAuthService.addUserDefaultRole(user.getId());
    }

    /**
     * 通过手机号查询相关用户
     *
     * @param phone
     * @return
     */
    public User getUserByPhone(String phone) {
        return userDao.getUserByPhone(phone);
    }

    /**
     * 登录成功，返回登录token
     *
     * @param user
     * @return
     * @throws Exception
     */
    public String login(User user) throws Exception {
        // 获取用户手机号
        String phone = user.getPhone() == null ? "" : user.getPhone();
        // 获取用户的邮箱
        String email = user.getEmail() == null ? "" : user.getEmail();
        if (StringUtils.isNullOrEmpty(phone) && StringUtils.isNullOrEmpty(email)) {
            throw new ConditionException("参数异常！");
        }
        // 通过手机号和邮箱查询相关用户
        User dbUser = userDao.getUserByPhoneOrEmail(phone, email);
        if (dbUser == null) {
            throw new ConditionException("当前用户不存在");
        }
        // 这个密码是经过前端RSA公钥加密之后传过来的，需要进行解密
        String password = user.getPassword();
        String rawPassword;
        try {
            // 通过RSA私钥解密，得到用户的原文密码
            rawPassword = RSAUtil.decrypt(password);
        } catch (Exception e) {
            throw new ConditionException("密码解密失败！");
        }
        // 从数据库中拿到用户的盐值
        String salt = dbUser.getSalt();
        // 将用户 原文密码+盐值 进行MD5加密
        String md5Password = MD5Util.sign(rawPassword, salt, "UTF-8");
        if (!md5Password.equals(dbUser.getPassword())) {
            throw new ConditionException("密码错误！");
        }
        // 生成用户登录令牌
        return TokenUtil.generateToken(dbUser.getId());
    }

    /**
     * 根据用户id查询用户信息
     *
     * @param userId
     * @return
     */
    public User getUserInfo(Long userId) {
        User user = userDao.getUserById(userId);
        UserInfo userInfo = userDao.getUserInfoByUserId(userId);
        user.setUserInfo(userInfo);
        return user;
    }

    /**
     * 更新用户信息
     *
     * @param user
     * @throws Exception
     */
    public void updateUsers(User user) throws Exception {
        Long id = user.getId();
        User dbUser = userDao.getUserById(id);
        if (dbUser == null) {
            throw new ConditionException("用户不存在！");
        }
        if (!StringUtils.isNullOrEmpty(user.getPassword())) {
            // 根据前端传过来的RSA加密的密码，进行解密，获取原始密码
            String rawPassword = RSAUtil.decrypt(user.getPassword());
            // 将原始密码和盐值进行md5加密并传入到后端数据库中
            String md5Password = MD5Util.sign(rawPassword, dbUser.getSalt(), "UTF-8");
            user.setPassword(md5Password);
        }
        user.setUpdateTime(new Date());
        userDao.updateUsers(user);
    }

    public void updateUserInfos(UserInfo userInfo) {
        // 设置更新时间
        userInfo.setUpdateTime(new Date());
        userDao.updateUserInfos(userInfo);
    }

    public User getUserById(Long followingId) {
        return userDao.getUserById(followingId);
    }

    public List<UserInfo> getUserInfoByUserIds(Set<Long> userIdList) {
        return userDao.getUserInfoByUserIds(userIdList);
    }

    public PageResult<UserInfo> pageListUserInfos(JSONObject params) {
        Integer no = params.getInteger("no");
        Integer size = params.getInteger("size");
        // 当前页的第一条数据在数据库中的位置
        params.put("start", (no - 1) * size);
        params.put("limit", size);
        // 获取总的用户条数
        Integer total = userDao.pageCountUserInfos(params);
        List<UserInfo> list = new ArrayList<>();
        if (total > 0) {
            // 分页查询数据
            list = userDao.pageListUserInfos(params);
        }
        return new PageResult<>(total, list);
    }

    /**
     * 双token用户登录
     *
     * @param user
     * @return
     * @throws Exception
     */
    public Map<String, Object> loginForDts(User user) throws Exception {
        String phone = user.getPhone() == null ? "" : user.getPhone();
        String email = user.getEmail() == null ? "" : user.getEmail();
        if (StringUtils.isNullOrEmpty(phone) && StringUtils.isNullOrEmpty(email)) {
            throw new ConditionException("参数异常！");
        }
        User dbUser = userDao.getUserByPhoneOrEmail(phone, email);
        if (dbUser == null) {
            throw new ConditionException("当前用户不存在");
        }
        // 这个密码是经过前端RSA加密之后传过来的，需要进行解密
        String password = user.getPassword();
        // 原文密码(解密后的密码)
        String rawPassword;
        try {
            rawPassword = RSAUtil.decrypt(password);
        } catch (Exception e) {
            throw new ConditionException("密码解密失败！");
        }
        // 从数据库中拿到对应用户的盐值
        String salt = dbUser.getSalt();
        String md5Password = MD5Util.sign(rawPassword, salt, "UTF-8");
        if (!md5Password.equals(dbUser.getPassword())) {
            throw new ConditionException("密码错误！");
        }
        Long userId = dbUser.getId();
        // 生成用户访问token
        String accessToken = TokenUtil.generateToken(userId);
        // 生成刷新token
        String refreshToken = TokenUtil.generateRefreshToken(userId);
        // 保存refresh token到数据库中，采用先删除，再插入的方式(插入，更新)
        /**
         * 将刷新token和userId一起保存到数据库中，这样做的目的是：
         * 方便我们后续用户在退出登录之后，或者想要延长访问token的有效期的时候，
         * 我们可以去查找跟userId相关联的刷新token，进行判断，
         * 如果刷新token存在，说明我们的刷新token依然在有效期中，可以延长我们的访问token(刷新访问token)
         * 如果刷新token不存在，说明我们的刷新token已经失效了，这个时候就要告诉前端(您已经登录失效，需要重新登录)
         */
        userDao.deleteRefreshToken(refreshToken, userId);
        userDao.addRefreshToken(refreshToken, userId, new Date());
        Map<String, Object> result = new HashMap<>();
        result.put("accessToken", accessToken);
        result.put("refreshToken", refreshToken);
        return result;
    }

    /**
     * 退出登录
     *
     * @param refreshToken 刷新token
     * @param userId       用户id
     */
    public void logout(String refreshToken, Long userId) {
        // 删除刷新token
        userDao.deleteRefreshToken(refreshToken, userId);
    }

    /**
     * 根据传入的刷新token，先校验刷新token是否已经失效，
     * 如果没有失效，就重新生成一个访问token，
     * 如果刷新token失效了，还是直接抛出token过期的异常给前端
     *
     * @param refreshToken
     * @return
     * @throws Exception
     */
    public String refreshAccessToken(String refreshToken) throws Exception {
        // 获取刷新令牌详情
        RefreshTokenDetail refreshTokenDetail = userDao.getRefreshTokenDetail(refreshToken);
        // 如果刷新token等于null，要告诉前端该刷新token是一个失效状态，不能刷新访问token
        if (refreshTokenDetail == null) {
            throw new ConditionException("555", "token过期！");
        }
        // 获取userId
        Long userId = refreshTokenDetail.getUserId();
        // 根据userId获取新的访问token
        return TokenUtil.generateToken(userId);
    }

    public List<UserInfo> batchGetUserInfoByUserIds(Set<Long> userIdList) {
        return userDao.batchGetUserInfoByUserIds(userIdList);
    }

    /**
     * 根据userId获取刷新token
     *
     * @param userId
     * @return
     */
    public String getRefreshTokenByUserId(Long userId) {
        return userDao.getRefreshTokenByUserId(userId);
    }
}
